#include "wx/wxprec.h"
#include "wx/anidecod.h"
#include <stdlib.h>
#include <string.h>
#include "wx/arrimpl.cpp"

wxCURHandler wxANIDecoder::sm_handler;

class wxANIFrameInfo {
  public:
    wxANIFrameInfo( unsigned int delay = 0, int idx = -1 ) {
      m_delay = delay;
      m_imageIndex = idx;
    }
    unsigned int m_delay;
    int m_imageIndex;
};


WX_DEFINE_OBJARRAY( wxImageArray )
WX_DEFINE_OBJARRAY( wxANIFrameInfoArray )

wxANIDecoder::wxANIDecoder() {
}

wxANIDecoder::~wxANIDecoder() {
}

bool wxANIDecoder::ConvertToImage( unsigned int frame, wxImage *image ) const {
  unsigned int idx = m_info[frame].m_imageIndex;
  *image = m_images[idx];
  return image->IsOk();
}

wxSize wxANIDecoder::GetFrameSize( unsigned int ( frame ) ) const {
  return m_szAnimation;
}

wxPoint wxANIDecoder::GetFramePosition( unsigned int ( frame ) ) const {
  return wxPoint( 0, 0 );
}

wxAnimationDisposal wxANIDecoder::GetDisposalMethod( unsigned int ( frame ) ) const {
  return wxANIM_TOBACKGROUND;
}

long wxANIDecoder::GetDelay( unsigned int frame ) const {
  return m_info[frame].m_delay;
}

wxColour wxANIDecoder::GetTransparentColour( unsigned int frame ) const {
  unsigned int idx = m_info[frame].m_imageIndex;
  if( !m_images[idx].HasMask() ) {
    return wxNullColour;
  }
  return wxColour( m_images[idx].GetMaskRed(), m_images[idx].GetMaskGreen(), m_images[idx].GetMaskBlue() );
}

bool wxANIDecoder::CanRead( wxInputStream& stream ) const {
  wxInt32 FCC1, FCC2;
  wxUint32 datalen ;
  wxInt32 riff32;
  memcpy( &riff32, "RIFF", 4 );
  wxInt32 list32;
  memcpy( &list32, "LIST", 4 );
  wxInt32 ico32;
  memcpy( &ico32, "icon", 4 );
  wxInt32 anih32;
  memcpy( &anih32, "anih", 4 );
  stream.SeekI( 0 );
  if( !stream.Read( &FCC1, 4 ) ) {
    return false;
  }
  if( FCC1 != riff32 ) {
    return false;
  }
  while( stream.IsOk() ) {
    if( FCC1 == anih32 ) {
      return true;
    }
    stream.Read( &datalen, 4 );
    datalen = wxINT32_SWAP_ON_BE( datalen ) ;
    if( datalen % 2 == 1 ) {
      datalen ++ ;
    }
    if( ( FCC1 == riff32 ) || ( FCC1 == list32 ) ) {
      stream.Read( &FCC2, 4 );
    } else {
      stream.SeekI( stream.TellI() + datalen );
    }
    if( !stream.Read( &FCC1, 4 ) ) {
      return false;
    }
  }
  return false;
}

struct wxANIHeader {
  wxInt32 cbSizeOf;
  wxInt32 cFrames;
  wxInt32 cSteps;
  wxInt32 cx;
  wxInt32 cy;
  wxInt32 cBitCount;
  wxInt32 cPlanes;
  wxInt32 JifRate;
  wxInt32 flags;

  #ifdef WORDS_BIGENDIAN
  void AdjustEndianness() {
    wxInt32 * const start = ( wxInt32 * )this;
    wxInt32 * const end = start + sizeof( wxANIHeader ) / sizeof( wxInt32 );
    for( wxInt32 *p = start; p != end; p++ ) {
      *p = wxINT32_SWAP_ALWAYS( *p );
    }
  }
  #else
  void AdjustEndianness() { }
  #endif
};

bool wxANIDecoder::Load( wxInputStream& stream ) {
  wxInt32 FCC1, FCC2;
  wxUint32 datalen;
  unsigned int globaldelay = 0;
  wxInt32 riff32;
  memcpy( &riff32, "RIFF", 4 );
  wxInt32 list32;
  memcpy( &list32, "LIST", 4 );
  wxInt32 ico32;
  memcpy( &ico32, "icon", 4 );
  wxInt32 anih32;
  memcpy( &anih32, "anih", 4 );
  wxInt32 rate32;
  memcpy( &rate32, "rate", 4 );
  wxInt32 seq32;
  memcpy( &seq32, "seq ", 4 );
  stream.SeekI( 0 );
  stream.Read( &FCC1, 4 );
  if( FCC1 != riff32 ) {
    return false;
  }
  m_nFrames = 0;
  m_szAnimation = wxDefaultSize;
  m_images.Clear();
  m_info.Clear();
  while( stream.IsOk() ) {
    stream.Read( &datalen, 4 );
    datalen = wxINT32_SWAP_ON_BE( datalen );
    if( datalen % 2 == 1 ) {
      datalen++;
    }
    if( ( FCC1 == riff32 ) || ( FCC1 == list32 ) ) {
      stream.Read( &FCC2, 4 );
    } else if( FCC1 == anih32 ) {
      if( datalen != sizeof( wxANIHeader ) ) {
        return false;
      }
      if( m_nFrames > 0 ) {
        return false;
      }
      struct wxANIHeader header;
      stream.Read( &header, sizeof( wxANIHeader ) );
      header.AdjustEndianness();
      m_szAnimation = wxSize( header.cx, header.cy );
      m_nFrames = header.cSteps;
      if( m_nFrames == 0 ) {
        return false;
      }
      globaldelay = header.JifRate * 1000 / 60;
      m_images.Alloc( header.cFrames );
      m_info.Add( wxANIFrameInfo(), m_nFrames );
    } else if( FCC1 == rate32 ) {
      if( m_nFrames == 0 ) {
        return false;
      }
      wxASSERT( m_info.GetCount() == m_nFrames );
      for( unsigned int i = 0; i < m_nFrames; i++ ) {
        stream.Read( &FCC2, 4 );
        m_info[i].m_delay = wxINT32_SWAP_ON_BE( FCC2 ) * 1000 / 60;
      }
    } else if( FCC1 == seq32 ) {
      if( m_nFrames == 0 ) {
        return false;
      }
      wxASSERT( m_info.GetCount() == m_nFrames );
      for( unsigned int i = 0; i < m_nFrames; i++ ) {
        stream.Read( &FCC2, 4 );
        m_info[i].m_imageIndex = wxINT32_SWAP_ON_BE( FCC2 );
      }
    } else if( FCC1 == ico32 ) {
      wxImage image;
      if( !sm_handler.DoLoadFile( &image, stream, false, -1 ) ) {
        return false;
      }
      m_images.Add( image );
    } else {
      stream.SeekI( stream.TellI() + datalen );
    }
    stream.Read( &FCC1, 4 );
  }
  if( m_nFrames == 0 ) {
    return false;
  }
  if( m_nFrames == m_images.GetCount() ) {
    for( unsigned int i = 0; i < m_nFrames; i++ )
      if( m_info[i].m_imageIndex == -1 ) {
        m_info[i].m_imageIndex = i;
      }
  }
  for( unsigned int i = 0; i < m_nFrames; i++ )
    if( m_info[i].m_delay == 0 ) {
      m_info[i].m_delay = globaldelay;
    }
  if( m_szAnimation.GetWidth() == 0 || m_szAnimation.GetHeight() == 0 ) {
    m_szAnimation = wxSize( m_images[0].GetWidth(), m_images[0].GetHeight() );
  }
  return m_szAnimation != wxDefaultSize;
}
